/*
 * @Author: 13938960424 1758142861@qq.com
 * @Date: 2023-10-08 15:04:21
 * @LastEditors: 生俊甫 1758142861@qq.com
 * @LastEditTime: 2024-06-17 17:28:50
 * @FilePath: /sjf/new-xshell/online_backgammon/server/gobang_server.hpp
 * @Description: ttt
 */
#ifndef _M_SERVER_H__
#define _M_SERVER_H__

#include <iostream>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>
#include "../source/session.hpp"
#include "../source/user.hpp"
#include "../source/util.hpp"
#include "../db/db.hpp"
#include "../log/logger.hpp"

#define WWWROOT "../wwwroot/"
typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

class gobang_server
{
public:
    gobang_server(
        const std::string &host,
        const std::string &username,
        const std::string &password,
        const std::string dbname,
        const unsigned int port = 3306,
        const std::string &wwwroot = WWWROOT)
        : _web_root(wwwroot), _user_table(host, username, password, dbname, port), _session_manager(&_wsserver)
    {
        // 2.初始化日志等级
        _wsserver.set_access_channels(websocketpp::log::alevel::none);
        // 3.初始化asio调试器
        _wsserver.init_asio();
        _wsserver.set_reuse_addr(true);
        // 4.设置回调函数
        _wsserver.set_http_handler(std::bind(&gobang_server::http_callback, this, std::placeholders::_1));
        _wsserver.set_open_handler(std::bind(&gobang_server::wsopen_callback, this, std::placeholders::_1));
        _wsserver.set_close_handler(std::bind(&gobang_server::wsclose_callback, this, std::placeholders::_1));
        _wsserver.set_message_handler(std::bind(&gobang_server::wsmsg_callback, this, std::placeholders::_1, std::placeholders::_2));
    }
    // 启动服务器
    void start(int port)
    {
        // 5.设置监听接口
        _wsserver.listen(port);
        // 6.开始获取新连接
        _wsserver.start_accept();
        // 7.启动服务器
        _wsserver.run();
    }

private:
    // 静态资源请求处理
    void file_handler(wsserver_t::connection_ptr &conn)
    {
        // 1.获取到请求url资源路径,了解请求的内面内容名称
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        // 2.组合出实际路径 --> 根目录 + uri
        std::string realpath = _web_root + uri;
        // 3.判断如果请求的是一个根目录,那么我们添加一个后缀login.html让其返回到登录页面
        if (realpath.back() == '/')
            realpath += "login.html";
        // 4.读取文件内容
        Json::Value resp_json;
        std::string body;
        bool ret = util_file_read::read_file(realpath, body);
        // 文件不存在,返回404
        if (ret == false)
        {
            body += "<html>";
            body += "<head>";
            body += "<meta charset='UTF-8'/>";
            body += "</head>";
            body += "<body>";
            body += "<h1> Not Found </h1>";
            body += "</body>";
            conn->set_status(websocketpp::http::status_code::not_found);
            conn->set_body(body);
            return;
        }
        // 5.设计响应正文
        conn->set_body(body);
        conn->set_status(websocketpp::http::status_code::ok);
    }
    /**
     * 针对用户登录注册时的返回
     * conn:传入的web请求
     * result:返回情况(true,false)
     * code:返回的状态码
     * reason:返回给用户提示
    */
    void http_resp(wsserver_t::connection_ptr& conn,bool result,websocketpp::http::status_code::value code,const std::string& reason)
    {
        Json::Value resp_json;
        resp_json["result"] = result;
        resp_json["reason"] = reason;
        std::string resp_body;
        util_json::serialize(resp_json,resp_body);
        conn->set_body(resp_body);
        conn->set_status(code);
        conn->append_header("Content-Type", "application/json");
        return;
    }
    // 用户注册请求处理
    void reg(wsserver_t::connection_ptr &conn)
    {
        //1.获取用户注册请求
        websocketpp::http::parser::request req = conn->get_request();
        //2.进行注册请求正文
        std::string req_body = conn->get_request_body();
        //3.对正文进行反序列化得到用户名和密码
        Json::Value reg_info;
        bool ret = util_json::unserialize(req_body,reg_info);
        if(ret == false)
        {
            DBG_LOG("获取正文反序列化失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求格式错误");
        }
        //4.进行数据库中数据的新增操作
        if(reg_info["username"].isNull() || reg_info["password"].isNull())
        {
            DBG_LOG("请输入正确的账号或密码");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入正确的账号或密码");
        }
        ret = _user_table.user_register(reg_info);
        if(ret == false)
        {
            DBG_LOG("导入数据失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户名已存在,请重新选择");
        }
        //5.注册成功,返回200
        return http_resp(conn,true,websocketpp::http::status_code::ok,"注册成功");
    }
    // 用户登录请求处理
    void login(wsserver_t::connection_ptr &conn)
    {
        //1.获取用户登录请求
        //websocketpp::http::parser::request req = conn->get_request();
        std::string req_body = conn->get_request_body();
        //2.进行反序列化,获取请求中的正文
        Json::Value login_info;
        bool ret = util_json::unserialize(req_body,login_info);
        if(ret == false)
        {
            DBG_LOG("获取正文反序列化失败!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求格式错误");
        }
        //3.拿到用户名和密码进行校验
        if(login_info["username"].isNull() || login_info["password"].isNull())
        {
            DBG_LOG("请输入正确的账号或密码");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请输入正确的账号或密码");
        }
        ret = _user_table.user_login(login_info);
        if(ret == false)
        {
            DBG_LOG("用户名或密码错误!");
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户名或密码错误");
        }
        //4.给客户端创建会话session
        uint64_t uid = login_info["id"].asUInt64();
        session_ptr ssp = _session_manager.create_session(uid,LOGIN);
        if(ssp.get() == nullptr)
        {
            DBG_LOG("会话创建失败!");
            return http_resp(conn,false,websocketpp::http::status_code::internal_server_error,"会话创建失败");
        }
        //5.设置定时信息,SESSION_FORVERE
        _session_manager.set_session_timer(ssp->get_sid(),SESSION_TIMEOUT);
        //6.设置响应头部:Set-Cookie,将sessionid通过cookie返回
        std::string cookie_ssid = "SSID=" + std::to_string(ssp->get_sid());
        conn->append_header("Set-Cookie",cookie_ssid);
        return http_resp(conn,true,websocketpp::http::status_code::ok,"登录成功");
    }
    //获取cookie信息
    bool get_cookie_val(const std::string& cookie_str,const std::string& key,std::string& val)
    {
        // Cookie: SSID=XXX; path=/; 
        //1. 以 ; 作为间隔，对字符串进⾏分割，得到各个单个的cookie信息
        std::string sep = "; ";
        std::vector<std::string> cookie_arr;
        util_split::split(cookie_str,sep,cookie_arr);
        for(auto str : cookie_arr)
        {
            //2. 对单个cookie字符串，以 = 为间隔进⾏分割，得到key和val
            std::vector<std::string> tmp_arr;
            util_split::split(str,"=",tmp_arr);
            if(tmp_arr.size() != 2) { continue; }
            if(tmp_arr[0] == key)
            {
                val = tmp_arr[1];
                return true;
            }
        }
        return false;
    }
    // 用户信息获取请求处理
    void info(wsserver_t::connection_ptr &conn)
    {
        //1.获取用户信息请求
        //websocketpp::http::parser::request req = conn->get_request();
        //2.从请求信息中获取cookie字段，并获取其id
        std::string cookie_str = conn->get_request_header("Cookie");
        if(cookie_str.empty())
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到cookie信息,请重新登录");
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
        if(ret == false)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到cookie信息,请重新登录");
        //3.在session中查找该会话
        session_ptr ssp = _session_manager.get_session_by_sid(std::stol(ssid_str));
        if(ssp.get() == nullptr)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"登录过期,请重新登录");
        //4.从会话中获取用户id
        uint64_t uid = ssp->get_user();
        Json::Value user_info;
        ret = _user_table.select_user_by_id(uid,user_info);
        if(ret == false)
            return http_resp(conn,false,websocketpp::http::status_code::bad_request,"找不到该用户信息,请重新登录");
        //5.从数据库中获取用户信息，发送给客户端
        std::string body;
        util_json::serialize(user_info,body);
        conn->set_body(body);
        conn->append_header("Content-Type", "application/json");
        conn->set_status(websocketpp::http::status_code::ok);
        //6.刷新session的过期时间
        _session_manager.set_session_timer(ssp->get_sid(),SESSION_TIMEOUT);
    }
    //对于http不同请求的处理
    void http_callback(websocketpp::connection_hdl hdl)
    {
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string method = req.get_method();
        std::string uri = req.get_uri();
        if(method == "POST" && uri == "/reg")
            return reg(conn);
        else if(method == "POST" && uri == "/login")
            return login(conn);
        else if(method == "GET" && uri == "/info")
            return info(conn);
        else 
            return file_handler(conn);
    }
    //对websocket的响应
    void ws_resp(wsserver_t::connection_ptr conn, Json::Value &resp) 
    {
        std::string body;
        util_json::serialize(resp, body);
        conn->send(body);
    } 
    //通过cookie获取连接信息
    session_ptr get_session_by_cookie(wsserver_t::connection_ptr conn)
    {
        Json::Value resp;
        //1.先从请求信息中获取cookie字段,并从cookie字段中获得ssid
        std::string cookie_str = conn->get_request_header("Cookie");
        //2.如果没有cookie信息,那么返回错误信息,让该客户端重新登录
        if(cookie_str.empty())
        {
            resp["optype"] = "hall_ready";
            resp["reason"] = "没有找到cookie信息,需要重新登录";
            resp["result"] = false;
            ws_resp(conn,resp);
            return session_ptr();
        }
        //3.从cookie字段中获得ssid
        std::string ssid_str;
        bool ret = get_cookie_val(cookie_str,"SSID",ssid_str);
        //4.如果cookie字段中没有ssid,返货错误信息,没有ssid,让该客户端重新登陆
        if(ret == false)
        {
            resp["optype"] = "hall_ready";
            resp["reason"] = "没有找到ssid,需要重新登录";
            resp["result"] = false;
            ws_resp(conn,resp);
            return session_ptr();
        }
        //5.在session管理中查找对应的会话信息
        session_ptr ssp = _session_manager.get_session_by_sid(std::stol(ssid_str));
        if(ssp.get() == nullptr)
        {
            resp["optype"] = "hall_ready";
            resp["reason"] = "没有找到session信息,需要重新登录";
            resp["result"] = false;
            ws_resp(conn,resp);
            return session_ptr();
        }
        return ssp;
    }
    //游戏大厅长连接建立完成
    void wsopen_game_hall(wsserver_t::connection_ptr& conn)
    {
        Json::Value resp_json;
        //1.登录验证，验证当前客户端是否正常登录
        session_ptr ssp = get_session_by_cookie(conn);
        if(ssp.get() == false)
            return;
        //2.再判断是否重复登陆,也就是多次登录
        if(_online_user.user_in_hall(ssp->get_user()))
        {
            resp_json["optype"] = "hall_ready";
            resp_json["reason"] = "玩家重复登录！";
            resp_json["result"] = false;
            return ws_resp(conn,resp_json);
        }
        //3.将该客户端连接到游戏大厅
        _online_user.get_game_hall(ssp->get_user(),conn);
        //4.给客户端响应游戏大厅连接成功
        resp_json["optype"] = "hall_ready";
        resp_json["result"] = true;
        ws_resp(conn, resp_json);
        //5.将该会话设置成永久存在,也就是长连接
        _session_manager.set_session_timer(ssp->get_sid(),SESSION_FOREVER);
    }
    //websocket长连接建立成功后的处理
    void wsopen_callback(websocketpp::connection_hdl hdl)
    {
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req = conn->get_request();
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsopen_game_hall(conn);
    }
    //游戏大厅长连接关闭完成
    void wsclose_game_hall(wsserver_t::connection_ptr& conn)
    {
        //1.登录验证-确保当前客户端是登录状态的
        session_ptr ssp = get_session_by_cookie(conn);
        if(ssp.get() == nullptr)
            return;
        //2.将该客户端从游戏大厅中移除
        _online_user.exit_game_hall(ssp->get_user());
        //3.恢复该会话的生命周期,也就是定时销毁
        _session_manager.set_session_timer(ssp->get_sid(),SESSION_TIMEOUT);
    }
    //websocket长连接断开前的处理
    void wsclose_callback(websocketpp::connection_hdl hdl)
    {
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req =  conn->get_request();
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsclose_game_hall(conn);
    }
    //游戏大厅长连接通信处理
    void wsmsg_game_hall(wsserver_t::connection_ptr& conn, wsserver_t::message_ptr msg)
    {
        Json::Value resp_json;
        std::string resp_body;
        //1.身份验证
        session_ptr ssp = get_session_by_cookie(conn);
        if(ssp.get() == nullptr) 
            return;
        //2.获取请求信息
        std::string req_body = msg->get_payload();
        Json::Value req_json;
        bool ret = util_json::unserialize(req_body,req_json);
        if(ret == false)
        {
            resp_json["result"] = false;
            resp_json["reason"] = "请求信息解析失败";
            return ws_resp(conn,resp_json);
        }
        //3.对信息进行处理(游戏大厅目前只有开始匹配和停止匹配两个处理)
        //4.未知请求
        resp_json["optype"] = "unknow";
        resp_json["reason"] = "请求类型未知";
        resp_json["result"] = false;
        return ws_resp(conn, resp_json);
    }
    //websocket长连接通信时的处理
    void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)
    {
        wsserver_t::connection_ptr conn = _wsserver.get_con_from_hdl(hdl);
        websocketpp::http::parser::request req =  conn->get_request();
        std::string uri = req.get_uri();
        if(uri == "/hall")
            return wsmsg_game_hall(conn,msg);
    }

private:
    std::string _web_root;            // 静态资源根目录
    user_table _user_table;           // 用户数据
    online_user _online_user;         // 在线用户
    session_manager _session_manager; // 会话管理(长连接)
    wsserver_t _wsserver;             // 实例出websocket对象
};

#endif